﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace SBS_Map_Editor
{
    public sealed partial class Manipulator
    {
        private void InitScale()
        {
            Dictionary<AxisFlags, Matrix> axis_transforms = new Dictionary<AxisFlags, Matrix>();

            axis_transforms[AxisFlags.X] = Matrix.CreateRotationZ(-MathHelper.PiOver2);
            axis_transforms[AxisFlags.Y] = Matrix.Identity;
            axis_transforms[AxisFlags.Z] = Matrix.CreateRotationX(MathHelper.PiOver2);
            axis_transforms[AxisFlags.X | AxisFlags.Y] = Matrix.Identity;
            axis_transforms[AxisFlags.X | AxisFlags.Z] = Matrix.CreateRotationX(MathHelper.PiOver2);
            axis_transforms[AxisFlags.Y | AxisFlags.Z] = Matrix.CreateRotationY(-MathHelper.PiOver2);

            #region Draw Single
            Action<Matrix> base_draw_axis = new Action<Matrix>(delegate(Matrix transform)
            {
                GraphicsDevice device = mGraphics.GraphicsDevice;
                ManipulatorSettings.ScaleSettings settings = mSettings.Scale;

                if ((settings.AxisDrawMode & AxisDirections.Positive) == AxisDirections.Positive)
                {
                    Primitives.DrawLine(device, Vector3.Zero, Vector3.UnitY * settings.AxisExtent, transform);
                    Primitives.DrawBox(device, Vector3.UnitY * settings.AxisExtent,
                        Vector3.One * settings.AxisHandleSize, transform);
                }

                if ((settings.AxisDrawMode & AxisDirections.Negative) == AxisDirections.Negative)
                {
                    Primitives.DrawLine(device, Vector3.Zero, -Vector3.UnitY * settings.AxisExtent, transform);
                    Primitives.DrawBox(device, -Vector3.UnitY * settings.AxisExtent,
                        Vector3.One * settings.AxisHandleSize, transform);
                }
            });

            mDrawFunctions[TransformationMode.ScaleAxis][AxisFlags.X]
                = mDrawFunctions[TransformationMode.ScaleAxis][AxisFlags.Y]
                = mDrawFunctions[TransformationMode.ScaleAxis][AxisFlags.Z]
                    = delegate(AxisFlags axis)
                    {
                        base_draw_axis(axis_transforms[axis]);
                    };
            #endregion

            #region Draw Dual
            Action<Matrix> base_draw_plane = new Action<Matrix>(delegate(Matrix transform)
            {
                GraphicsDevice device = mGraphics.GraphicsDevice;
                ManipulatorSettings.ScaleSettings settings = mSettings.Scale;

                bool draw_pos = (settings.AxisDrawMode & AxisDirections.Positive) == AxisDirections.Positive;
                bool draw_neg = (settings.AxisDrawMode & AxisDirections.Negative) == AxisDirections.Negative;

                if (draw_pos)
                {
                    Primitives.DrawLine(device, Vector3.UnitY * settings.AxisExtent,
                        Vector3.UnitX * settings.AxisExtent, transform);
                    Primitives.DrawSphere(device, Vector3.UnitY * settings.AxisExtent * 0.5f
                        + Vector3.UnitX * settings.AxisExtent * 0.5f, settings.PlaneHandleRadius, 10, 20, transform);
                }

                if (draw_neg)
                {
                    Primitives.DrawLine(device, -Vector3.UnitY * settings.AxisExtent,
                        -Vector3.UnitX * settings.AxisExtent, transform);
                    Primitives.DrawSphere(device, -Vector3.UnitY * settings.AxisExtent * 0.5f
                        + -Vector3.UnitX * settings.AxisExtent * 0.5f, settings.PlaneHandleRadius, 10, 20, transform);
                }

                if (draw_pos && draw_neg)
                {
                    Primitives.DrawLine(device, -Vector3.UnitY * settings.AxisExtent,
                        Vector3.UnitX * settings.AxisExtent, transform);
                    Primitives.DrawSphere(device, -Vector3.UnitY * settings.AxisExtent * 0.5f
                        + Vector3.UnitX * settings.AxisExtent * 0.5f, settings.PlaneHandleRadius, 10, 20, transform);

                    Primitives.DrawLine(device, Vector3.UnitY * settings.AxisExtent,
                        -Vector3.UnitX * settings.AxisExtent, transform);
                    Primitives.DrawSphere(device, Vector3.UnitY * settings.AxisExtent * 0.5f
                        + -Vector3.UnitX * settings.AxisExtent * 0.5f, settings.PlaneHandleRadius, 10, 20, transform);
                }
            });

            mDrawFunctions[TransformationMode.ScalePlane][AxisFlags.XY]
                = mDrawFunctions[TransformationMode.ScalePlane][AxisFlags.XZ]
                = mDrawFunctions[TransformationMode.ScalePlane][AxisFlags.YZ]
                    = delegate(AxisFlags axis)
                    {
                        base_draw_plane(axis_transforms[axis]);
                    };
            #endregion

            #region Draw Uniform
            mDrawFunctions[TransformationMode.ScaleUniform][AxisFlags.X | AxisFlags.Y | AxisFlags.Z]
                = delegate(AxisFlags axis)
                {
                    ManipulatorSettings.ScaleSettings settings = mSettings.Scale;
                    Primitives.DrawSphere(mGraphics.GraphicsDevice, Vector3.Zero,
                        settings.UniformHandleRadius, 10, 20);
                };
            #endregion

            #region Single Scaling
            mManipFunctions[TransformationMode.ScaleAxis][AxisFlags.X]
            = mManipFunctions[TransformationMode.ScaleAxis][AxisFlags.Y]
            = mManipFunctions[TransformationMode.ScaleAxis][AxisFlags.Z]
            = delegate()
            {
                Vector3 axis = GetUnitAxis(SelectedAxes);

                Matrix tmtx = Matrix.CreateTranslation(mTransform.Translation);

                Vector3 p0 = Viewport.Project(Vector3.Zero, ProjectionMatrix, ViewMatrix, tmtx);
                Vector3 p1 = Viewport.Project(Vector3.Zero, ProjectionMatrix, ViewMatrix, tmtx);

                p0.Z = p1.Z = 0;

                Vector3 ps = new Vector3(mInput.Start, 0);
                Vector3 pe = new Vector3(mInput.End, 0);

                Vector3 v0 = p1 - p0;
                Vector3 vs = ps - p0;
                Vector3 ve = pe - p0;

                float proj_s = Math.Abs(Vector3.Dot(vs, v0) / v0.Length());
                float proj_e = Math.Abs(Vector3.Dot(ve, v0) / v0.Length());

                Vector3 scale = mTransform.Scale;
                float ratio = (proj_e / proj_s);

                switch (SelectedAxes)
                {
                    case AxisFlags.X:
                        scale.X *= ratio;
                        break;
                    case AxisFlags.Y:
                        scale.Y *= ratio;
                        break;
                    case AxisFlags.Z:
                        scale.Z *= ratio;
                        break;
                }

                scale.X = MathHelper.Clamp(scale.X, float.Epsilon, float.MaxValue);
                scale.Y = MathHelper.Clamp(scale.Y, float.Epsilon, float.MaxValue);
                scale.Z = MathHelper.Clamp(scale.Z, float.Epsilon, float.MaxValue);

                mTransform.Scale = scale;
            };
            #endregion

            #region Dual Scaling
            mManipFunctions[TransformationMode.ScalePlane][AxisFlags.X | AxisFlags.Y]
            = mManipFunctions[TransformationMode.ScalePlane][AxisFlags.X | AxisFlags.Z]
            = mManipFunctions[TransformationMode.ScalePlane][AxisFlags.Y | AxisFlags.Z]
            = delegate()
            {
                Plane p = GetPlane(SelectedAxes);

                Ray start_ray = GetPickRay(mInput.Start);
                Ray end_ray = GetPickRay(mInput.End);

                float? start_hit = start_ray.Intersects(p);
                float? end_hit = end_ray.Intersects(p);

                if (!start_hit.HasValue || !end_hit.HasValue)
                    return;

                Vector3 start_pos = start_ray.Position + (start_ray.Direction * start_hit.Value);
                Vector3 end_pos = end_ray.Position + (end_ray.Direction * end_hit.Value);

                Vector3 start_to_pos = start_pos - mTransform.Translation;
                Vector3 end_to_pos = end_pos - mTransform.Translation;

                float start_len = start_to_pos.Length();
                float end_len = end_to_pos.Length();

                Vector3 scale = mTransform.Scale;
                float ratio = (start_len == 0) ? (1) : (end_len / start_len);

                if ((SelectedAxes & AxisFlags.X) == AxisFlags.X)
                    scale.X *= ratio;
                if ((SelectedAxes & AxisFlags.Y) == AxisFlags.Y)
                    scale.Y *= ratio;
                if ((SelectedAxes & AxisFlags.Z) == AxisFlags.Z)
                    scale.Z *= ratio;

                scale.X = MathHelper.Clamp(scale.X, float.Epsilon, float.MaxValue);
                scale.Y = MathHelper.Clamp(scale.Y, float.Epsilon, float.MaxValue);
                scale.Z = MathHelper.Clamp(scale.Z, float.Epsilon, float.MaxValue);

                mTransform.Scale = scale;
            };
            #endregion

            #region Uniform Scaling
            mManipFunctions[TransformationMode.ScaleUniform][AxisFlags.X | AxisFlags.Y | AxisFlags.Z]
            = delegate()
            {
                Vector3 pos_to_cam = Matrix.Invert(ViewMatrix).Translation - mTransform.Translation;

                if (pos_to_cam != Vector3.Zero)
                    pos_to_cam.Normalize();

                Plane p = Plane.Transform(new Plane(pos_to_cam, 0), Matrix.CreateTranslation(mTransform.Translation));

                Ray start_ray = GetPickRay(mInput.Start);
                Ray end_ray = GetPickRay(mInput.End);

                float? start_hit = start_ray.Intersects(p);
                float? end_hit = end_ray.Intersects(p);

                if (!start_hit.HasValue || !end_hit.HasValue)
                    return;

                Vector3 start_pos = start_ray.Position + (start_ray.Direction * start_hit.Value);
                Vector3 end_pos = end_ray.Position + (end_ray.Direction * end_hit.Value);

                Vector3 start_to_pos = start_pos - mTransform.Translation;
                Vector3 end_to_pos = end_pos - mTransform.Translation;

                float start_len = start_to_pos.Length();
                float end_len = end_to_pos.Length();

                Vector3 scale = mTransform.Scale;
                float ratio = (start_len == 0) ? (1) : (end_len / start_len);

                if ((SelectedAxes & AxisFlags.X) == AxisFlags.X)
                    scale.X *= ratio;
                if ((SelectedAxes & AxisFlags.Y) == AxisFlags.Y)
                    scale.Y *= ratio;
                if ((SelectedAxes & AxisFlags.Z) == AxisFlags.Z)
                    scale.Z *= ratio;

                scale *= ratio;

                scale.X = MathHelper.Clamp(scale.X, float.Epsilon, float.MaxValue);
                scale.Y = MathHelper.Clamp(scale.Y, float.Epsilon, float.MaxValue);
                scale.Z = MathHelper.Clamp(scale.Z, float.Epsilon, float.MaxValue);

                mTransform.Scale = scale;
            };
            #endregion
        }
    }
}
